﻿namespace MicroCloud.Authorization
{
    /// <summary>
    /// 实体信息处理基类
    /// </summary>
    /// <typeparam name="TEntityInfo">实体信息类型</typeparam>
    public abstract class EntityInfoHandlerBase<TEntityInfo> : IEntityInfoHandler
        where TEntityInfo : class, IEntityInfo, new()
    {
        #region 属性
        //受保护属性
        /// <summary>
        /// 获取 服务提供者对象
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }

        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger Logger => ServiceProvider.GetLogger(GetType());

        /// <summary>
        /// 获取 实体信息集合
        /// </summary>
        protected List<TEntityInfo> EntityInfos { get; private set; } = new();

        /// <summary>
        /// 获取 实体类型字典
        /// </summary>
        protected Dictionary<string, Type> EntityTypeDict { get; private set; } = new();

        #endregion

        #region 构造函数
        /// <summary>
        /// 初始化一个实体信息处理基类 <see cref="EntityInfoHandlerBase{TEntityInfo}"/> 的新实例
        /// </summary>
        /// <param name="serviceProvider">服务提供者</param>
        protected EntityInfoHandlerBase(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }

        #endregion

        #region 方法
        /// <summary>
        /// 从程序集中刷新实体信息
        /// <para>实现了 <see cref="IEntity{TKey}"/> 接口的实体类</para>
        /// </summary>
        public void Initialize()
        {
            var entityTypes = GetEntityTypes();
            Logger.LogInformation("初始化数据实体处理器，获取到 {length} 个实体类。", entityTypes.Length);
            foreach (Type entityType in entityTypes)
            {
                var typeName = entityType.GetFullNameWithModule();
                if (!EntityTypeDict.ContainsKey(typeName))
                {
                    EntityTypeDict.Add(typeName, entityType);
                }

                if (EntityInfos.Exists(m => m.TypeName == typeName))
                {
                    continue;
                }

                TEntityInfo entityInfo = new();
                entityInfo.FromType(entityType, ServiceProvider);
                EntityInfos.Add(entityInfo);
                Logger.LogDebug("提取实体信息 {entityInfo}", entityInfo);
            }

            ServiceProvider.ExecuteScopedWork(provider =>
            {
                SyncToDatabase(provider, EntityInfos);
            });

            RefreshCache();
        }

        /// <summary>
        /// 查找指定实体类型的实体信息
        /// </summary>
        /// <param name="type">实体类型</param>
        /// <returns>实体信息</returns>
        public IEntityInfo GetEntityInfo(Type type)
        {
            Check.NotNull(type, nameof(type));
            if (EntityInfos.Count == 0)
            {
                RefreshCache();
            }

            string typeName = type.GetFullNameWithModule();
            IEntityInfo entityInfo = EntityInfos.FirstOrDefault(m => m.TypeName == typeName);
            if (entityInfo != null)
            {
                return entityInfo;
            }

            if (type.BaseType == null)
            {
                return null;
            }

            typeName = type.BaseType.GetFullNameWithModule();
            return EntityInfos.FirstOrDefault(m => m.TypeName == typeName);
        }

        /// <summary>
        /// 查找指定实体类型的实体信息
        /// </summary>
        /// <typeparam name="TEntity">实体类型</typeparam>
        /// <typeparam name="TKey">实体主键类型</typeparam>
        /// <returns>实体信息</returns>
        public IEntityInfo GetEntityInfo<TEntity, TKey>() where TEntity : IEntity<TKey> where TKey : IEquatable<TKey>
        {
            Type type = typeof(TEntity);
            return GetEntityInfo(type);
        }

        /// <summary>
        /// 刷新实体信息缓存
        /// </summary>
        public void RefreshCache()
        {
            ServiceProvider.ExecuteScopedWork(provider =>
            {
                EntityInfos.Clear();
                EntityInfos.AddRange(GetFromDatabase(provider));
                Logger.LogInformation("刷新实体信息缓存，从数据库中获取到 {count} 个实体信息。", EntityInfos.Count);
            });
        }

        /// <summary>
        /// 查找指定名称的实体类型
        /// </summary>
        /// <param name="typeName">类型名称</param>
        /// <returns>实体类型</returns>
        public Type GetEntityType(string typeName)
        {
            Check.NotNullOrEmpty(typeName, nameof(typeName));

            if (!EntityTypeDict.TryGetValue(typeName, out Type value))
            {
                return null;
            }
            return value;
        }

        #endregion

        #region 受保护的方法
        /// <summary>
        /// 获取实体类型数组
        /// </summary>
        /// <returns></returns>
        protected virtual Type[] GetEntityTypes()
        {
            //过滤仅用于系统控制的实体类型
            //Type[] filters = new Type[] { typeof(EntityInfo), typeof(Function) };
            //var types = AssemblyManager.FindTypesByBase(typeof(IEntity<>)).Where(m => !filters.Contains(m)).ToArray();
            var types = AssemblyManager.FindTypesByBase(typeof(IEntity<>));
            return types;
        }
        
        /// <summary>
        /// 将从程序集获取的实体信息同步到数据库
        /// </summary>
        /// <param name="scopedProvider"></param>
        /// <param name="entityInfos"></param>
        protected virtual void SyncToDatabase(IServiceProvider scopedProvider, List<TEntityInfo> entityInfos)
        {
            var repository = scopedProvider.GetService<IRepository<TEntityInfo, long>>();
            if (repository == null)
            {
                Logger.LogWarning("初始化实体数据时，IRepository<,> 的服务未找到，请初始化 EntityFrameworkCoreModule 模块");
                return;
            }

            IUnitOfWork unitOfWork = scopedProvider.GetUnitOfWork(true);

            //检查指定实体的Hash值，决定是否需要进行数据库同步
            if (!entityInfos.CheckSyncByHash(scopedProvider, Logger, typeof(TEntityInfo), GetType(), "实体数据", "初始化实体数据") && repository.QueryAsNoTracking().Any() && entityInfos.Count > 0)
            {
                Logger.LogInformation("同步实体数据时，验证数据签名与上次相同，跳过同步。");
                return;
            }

            TEntityInfo[] dbItems = repository.Query(null, false).ToArray();

            //删除的实体信息
            TEntityInfo[] removeItems = dbItems.Except(entityInfos, EqualityHelper<TEntityInfo>.CreateComparer(m => m.TypeName)).ToArray();
            int removeCount = removeItems.Length;
            //TODO：由于外键关联不能物理删除的实体，需要实现逻辑删除
            foreach (TEntityInfo entityInfo in removeItems)
            {
                repository.Delete(entityInfo);
                Logger.LogDebug("删除实体信息 {name}[{typeName}]", entityInfo.Name, entityInfo.TypeName);
            }

            //处理新增的实体信息
            TEntityInfo[] addItems = entityInfos.Except(dbItems, EqualityHelper<TEntityInfo>.CreateComparer(m => m.TypeName)).ToArray();
            int addCount = addItems.Length;
            foreach (TEntityInfo entityInfo in addItems)
            {
                repository.Insert(entityInfo);
                Logger.LogDebug("新增实体信息 {name}[{typeName}]", entityInfo.Name, entityInfo.TypeName);
            }

            //处理更新的实体信息
            int updateCount = 0;
            foreach (TEntityInfo item in dbItems.Except(removeItems))
            {
                bool isUpdate = false;
                TEntityInfo entityInfo = entityInfos.SingleOrDefault(m => m.TypeName == item.TypeName);
                if (entityInfo == null)
                {
                    continue;
                }

                if (item.Name != entityInfo.Name)
                {
                    item.Name = entityInfo.Name;
                    isUpdate = true;
                }
                if (item.DbSchema != entityInfo.DbSchema)
                {
                    item.DbSchema = entityInfo.DbSchema;
                    isUpdate = true;
                }
                if (item.DbObjName != entityInfo.DbObjName)
                {
                    item.DbObjName = entityInfo.DbObjName;
                    isUpdate = true;
                }
                if (item.DbObjType != entityInfo.DbObjType)
                {
                    item.DbObjType = entityInfo.DbObjType;
                    isUpdate = true;
                }
                if (item.AuditEnabled != entityInfo.AuditEnabled)
                {
                    item.AuditEnabled = entityInfo.AuditEnabled;
                    isUpdate = true;
                }
                if (item.EventEnabled != entityInfo.EventEnabled)
                {
                    item.EventEnabled = entityInfo.EventEnabled;
                    isUpdate = true;
                }
                if (item.PropertyJson != entityInfo.PropertyJson)
                {
                    item.PropertyJson = entityInfo.PropertyJson;
                    isUpdate = true;
                }

                if (isUpdate)
                {
                    repository.Update(item);
                    Logger.LogDebug("更新实体信息 {name}[{typeName}]", entityInfo.Name, entityInfo.TypeName);
                    updateCount++;
                }
            }

            unitOfWork.Commit();
            if (removeCount + addCount + updateCount > 0)
            {
                string msg = "刷新实体信息：";
                if (addCount > 0)
                {
                    msg += $"添加 {addCount} 个实体信息。";
                }

                if (removeCount > 0)
                {
                    msg += $"删除 {removeCount} 个实体信息。";
                }

                if (updateCount > 0)
                {
                    msg += $"更新 {updateCount} 个实体信息。";
                }

                Logger.LogInformation("{msg}", msg);
            }
        }
        
        /// <summary>
        /// 从数据库获取最新实体信息
        /// </summary>
        /// <param name="scopedProvider"></param>
        /// <returns></returns>
        protected virtual TEntityInfo[] GetFromDatabase(IServiceProvider scopedProvider)
        {
            var repository = scopedProvider.GetService<IRepository<TEntityInfo, long>>();
            if (repository == null)
            {
                return Array.Empty<TEntityInfo>();
            }

            var entityInfos = repository.QueryAsNoTracking(null, false).ToArray();
            return entityInfos;
        }

        #endregion

    }

}